support-v7、appcompatでTheme(テーマ)とToolbar、PopupMenu、背景、文字の各デザイン変更あれこれ
ここでは必読漢詩集という、去年の5月から放置していたアプリを作成するにあたり、作成時の時点で日本語の情報がなかったり欠落していて、英語のドキュメントやコミュニティの投稿から見つけて参考にしたりしつつ、解決出来た部分とその方法を中心にメモ書きしています。
因みにAndroidプログラミング関連の書籍は、現時点でSDKのアップデートに追いついておらず、古い情報ばかりで、最新の情報は英語ドキュメントやどこかの英語のサイト、ブログやサンプルを参考にしないと情報を手に入れる事が出来ませんでした。
※ まぁどこかに情報は乗っていたかもしれないけど探し出せなかった。
※ あと、関係ないけどLollipop以前の端末でlollipop ripple effectを実装するライブラリも発見したけど、時間無いので試してません。
これ → Implementation of Ripple effect from Material Design for Android API 9+
必読漢詩集アプリ作成時に気付いた悩みどころあれこれメモ
とりあえず開発環境は下記の通り。
- Windows 8.1 64bit
- eclipse4.4.2
- Android SDK Tools 24.1.2
- Android SDK Platform-tools 22
- Android Build-tools 22.01
- Android SDK 5.1.1
- Android Support Library 22.1
- etc
端的にいうと2015年4月時点の最新バージョンのWindows8.1 64bit、Eclipse、Android SDKを利用しているという事です。
で、下記項目について簡単にメモしています。
AppCompatを利用して必読漢詩集アプリを作った時に手間取った箇所
- ActionBarの代替となるToolbarの使い方
- AppCompatを利用してTheme、各コンポーネントのデザインを変更する方法
- LogCatで「not enough space to show ad. needs・・」のLogが出てAdMob広告が表示されない
最後のAdMob広告の部分はAppCompatとは関係ないですが、AdMobの広告が出ない理由が分かったのでメモしてます。
※ 因みにこれが必読漢詩集のアプリです
ActionBarの代わりにToolBarを利用してみた
Lollipop以降の最新バージョンのSupport Library v7では、android.v7.widget.Toolbarを直接レイアウトファイルに指定する事でToolBarをただのViewとして利用出来るようになりました。
端的に言うと、ButtonやTextViewのように扱えるようになったという感じでしょうか。
うん。単純ですね。
今までの、Android 2系~4系までカバーする黒魔術的な記述作業が必要なくなったという事です。
日本語での情報ももちろんあります。
でもね、ここで書くって事は手順通りに試してダメだったので書いているって事なのです。
まぁプログラミングしてて一発で思い通りに動作した事なんて一回もない気がするわ。なぜかおかしい挙動をしたり、エラーで落ちる等、スムーズに行く事がありません(泣)
で、僕の場合、既存のレイアウトにandroid.support.v7.widget.Toolbar~を記述したら、
「そんなレイアウトありません」
的なエラーメッセージが出て、利用出来ず。
で、なぜか
- 新規で空のxmlのレイアウトファイルを作成
- ToolBarのみ記述
- 必要なコンポーネントを追加
- 最終的に同じレイアウトを構築
という単純な手順でエラーが出なくなり、問題なく使用出来るようになりました。
これはEclipseの問題なのかと思い、軽く調べたけど結局原因がわからなかったけど、解決したのでメモ。
ってか、Android Studioだとこんなエラー出ないですかね?ってか乗り換えなきゃいけないって事ですか?
まぁこのエラーで悩んでいる方は参考にして下さいw
さっそくsupport-v7のToolBarを使用
左の画面キャプチャのように、今回はActionBarとして利用するので、画面上部にToolBarのレイアウトを持って来た。
今回はシンプルに最低限の属性だけしか指定、変更していません。
// こんな感じ。まさに最低限ですねww
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize" />
Javaコードも至ってシンプル。setSupportActionBarメソッドを利用し、今回必要でないsetDisplayHomeAsUpEnabledとsetDisplayShowHomeEnabledをfalseにしてるだけです。
toolbar = (Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setDisplayShowHomeEnabled(false);
で、このToolBarを使うには、Theme.AppCompat.Light.NoActionBar、Lightでなくてもいいけど、NoActionBar、つまりActionBarがないテーマを継承する必要があります。
ActionBarがあるとエラーが出ます。
このあたりの情報は普通に日本語で多数のブログに情報が上がっているので割愛。
ついでにPopUpMenuの使い方
ToolBarにPopupMenu、メニューを追加する方法もいたってシンプル。
eclipseでは、これまで通りの方法でmenu.xmlを作成すれば使えます。
JavaのソースコードではonCreateOptionsMenuとonOptionsItemSelectedで対応可能。これは情報沢山あるし、今更過ぎるので詳細は割愛。
これだけでToolBarでメニューが使えるようになりました。
画像追加なども単純だし、日本語で情報普通に出てくるので割愛。
Android、AppCompatのテーマを上書きし、Themeを変更する方法
Androidが5.0、(Lollipop)にアップデートされ、SDK、Support Library、support-v7も新しくなり、AppCompatのテーマも新しくなったので、テーマのカスタマイズ学習ついでにThemeを変更できるようにしてみたのですが、その時に気付いた事、はまった箇所のメモ書きです。
下記は今回の必読漢詩集アプリの1テーマの構造です。これ以外の属性も指定出来ますが、とりあえずやり方が分かったし、必要なかったので必要最低限の属性のみ変更しています。
style.xmlでテーマ指定をする
<resources xmlns:android="https://schemas.android.com/apk/res/android">
<!-- ベースのテーマ --!>
<style name="IndigoTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:colorPrimaryDark">@color/indigo_900</item>
<item name="android:navigationBarColor">@color/indigo_900</item>
<item name="android:textColorPrimary">@color/indigo_900</item>
<item name="android:windowBackground">@color/indigo_50</item>
<item name="toolbarStyle">@style/IndigoToolbar</item>
<item name="windowActionModeOverlay">true</item>
</style>
<!-- ツールバーのテーマ --!>
<style name="IndigoToolbar" parent="Widget.AppCompat.Toolbar">
<item name="android:background">@color/indigo_500</item>
<item name="popupTheme">@style/PopupMenu_Indigo</item>
<item name="theme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
</style>
<!-- ポップアップメニューのテーマ --!>
<style name="PopupMenu_Indigo" parent="ThemeOverlay.AppCompat.Light">
<item name="android:background">@color/indigo_500</item>
<item name="android:textColor">@color/indigo_50</item>
</style>
</resources>
上記の構造を簡単に説明すると、
- 最下部のポップアップメニューのテーマをツールバーのテーマ内で指定
- そのツールバーのテーマをベースのテーマ内で指定
という事です。
上記sytle.xmlの内容を反映した画面の画像。図解で見ればすぐに分かりますよねw
ただ、ベーステーマ内のcolorPrimaryDarkとnavigationBarColorはテーマ変更時にはなぜか反映されないので、Javaのソースコード記述で対応してる為、実際にはstyle.xmlには記述していません。
Themeを動的に変更するのでなければ、colorPrimaryDarkとnavigationBarColorを指定してればそれが反映されたままになりますので問題ないと思います。
左は、Javaのソースコード内でsetStatusBarColorとsetNavigationBarColorを使用するため、getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);を記述。
これを指定していないと動的にテーマが変更されません。
右はテーマ毎にsetStatusBarColorとsetNavigationBarColorメソッドを使ってカラーを設定しています。
因みにnavigationBarColorはAndroid 5.0(Lollipop)以上でないと反映されないけど、わざわざvalues-v21フォルダ作成せずとも、valuesフォルダ内で指定していれば、
- Nexus 7 2013の4月25日時点の最新バージョン 5.1.1では反映されてる
- エミュレータ上のAndroid2.2でエラーなし
- SH-12C、Android2.3.3でエラーなし
- LogCatのログもエラーなし
という結果でした。
テーマを変更する具体的な方法
上記の情報を踏まえ、動的にThemeを変更するには、
setTheme(R.style.GreenTheme);
setContentView(R.layout.main);
このようにsetTheme(R.style.自作テーマ)を、setContentView(R.layout.自作レイアウト)の前に呼ぶ必要があります。そうしないとテーマが反映されません。
ListViewで選択されたテーマの取得と保存方法
どのThemeが選択されたか、また、その情報を保持する為に、SharedPreferencesを利用しています。
SharedPreferencesの使い方はシンプルですし、情報が沢山あるので割愛。
この必読漢詩集ではシンプルにListViewで、ずばりリスト形式でTheme一覧を表示しているので、Listener(リスナー)をセットし、第3引数のint arg2の値をSharedPreferencesのインスタンスに代入し、保存。
それを各ページで呼び出し、整数の値によってテーマをセットしているという構造です。
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
pref = getSharedPreferences("pref",MODE_PRIVATE);
editor = pref.edit();
editor.putInt("key", arg2);
editor.commit(); // これで書き込み完了
intent = new Intent(getApplicationContext(), KanshiMain.class);
// 因みにこれをセットする事でこれまで起動していたActivityがメモリ上から消去される
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
} // onItemClick
}); // setOnItemClickListenerはここまで
各Activity画面でThemeを設定(選択)する方法
getSharedPreferencesを利用して、i保存したintの値を取り出し、switchさせるという、至ってシンプルな方法です。
SharedPreferences pref;
private int prefInt;
~ 省略 ~
pref = getSharedPreferences("pref", MODE_PRIVATE);
prefInt = pref.getInt("key", 0);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
switch(prefInt){
case 0:
// 因みにこれは、Lollipop以上のバージョンであれば、上述のstyle.xmlで反映されなかった
// StatusBarColorとNavigationBarColorを設定する為の記述です。シンプルですね。
// あ、最初に上述の
// getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
// を記述するのを忘れずにね!
if(Build.VERSION.SDK_INT >= 21){
getWindow().setStatusBarColor(getResources().getColor(R.color.indigo_900));
getWindow().setNavigationBarColor(getResources().getColor(R.color.indigo_900));
}
setTheme(R.style.IndigoTheme);
break;
case 1:
if(Build.VERSION.SDK_INT >= 21){
getWindow().setStatusBarColor(getResources().getColor(R.color.pink_900));
getWindow().setNavigationBarColor(getResources().getColor(R.color.pink_900));
}
setTheme(R.style.PinkTheme);
break;
・
~ 省略 ~
・
//こんな感じでSharedPreferencesのインスタンスに保存した値を取り出し、switchさせてます
・
・
} // switch
// 上述のようにこれを呼ぶ前にsetThemeを呼ばないとThemeが反映されません
setContentView(R.layout.list);
AdModのAdSize.SMART_BANNERを利用して、表示されなかった原因メモ書き
えっと、なぜかいつも「このアプリについて」にだけ入れているAdMobの広告が表示されなかったのですが、原因が分かったのでメモ書き。
LogCatを見ると、「not enough space to show ad. needs~」という表示が出ていたので、Marginを消したら普通に表示されました。
高さや幅の指定の問題ではなく、別のレイアウトでMarginが設定されていると、AdSize.SMART_BANNERのバナーサイズの広告は表示されなかった。
因みにSMART_BANNERは320×50~1024×50のサイズの広告を自動で表示してくれる広告ユニットです。
今までは画面のサイズ、レイアウト毎に指定してたけど、楽になりましたねぇ。
えっと、コードがすごく見づらいから、後日時間作ってCSSをソースコードが見やすいように変更しようと思います。
あと、上記の情報がこのブログにたどり着いたアプリ製作者様、読者様のヒントになれば幸いです!
必読漢詩集のプロモーション動画
ニコ生風のプロモーション動画です。うぷしてる動画の中に3D動画のプロモーション動画もあります♪